#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
时间序列可视化工具
版本: 1.0.0
作者: TimeSeriesIQ 开发团队
日期: 2023-11-15
描述: 提供时间序列数据的可视化和交互式分析功能
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import statsmodels.api as sm
from datetime import datetime, timedelta
from time_series_analysis import TimeSeriesDataProcessor, TimeSeriesAnalyzer

class TimeSeriesVisualizer:
    """时间序列数据可视化类"""
    
    def __init__(self, data_processor=None):
        """
        初始化可视化器
        
        参数:
            data_processor (TimeSeriesDataProcessor): 数据预处理器实例
        """
        # 设置中文字体
        import matplotlib.pyplot as plt
        import matplotlib
        try:
            # 尝试设置微软雅黑字体
            plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
            plt.rcParams['axes.unicode_minus'] = False
        except:
            try:
                # 如果没有微软雅黑，尝试设置其他中文字体
                plt.rcParams['font.sans-serif'] = ['SimHei', 'SimSun', 'Arial Unicode MS']
                plt.rcParams['axes.unicode_minus'] = False
            except:
                print("警告: 无法设置中文字体，图表中的中文可能显示为乱码")
        
        self.dp = data_processor
        if data_processor is None:
            self.dp = TimeSeriesDataProcessor()
        
        self.analyzer = TimeSeriesAnalyzer(self.dp)
        self.color_palette = {
            'primary': '#1f77b4',
            'secondary': '#ff7f0e',
            'success': '#2ca02c',
            'danger': '#d62728',
            'info': '#9467bd',
            'warning': '#e377c2',
            'light': '#cccccc',
            'dark': '#333333',
            'background': '#f8f9fa'
        }

    def plot_overview(self, figsize=(15, 10)):
        """
        生成时间序列数据概览图
        
        参数:
            figsize (tuple): 图形大小
            
        返回:
            matplotlib.figure: 概览图
        """
        if self.dp.data is None or self.dp.target_column is None:
            print("请先加载数据")
            return None
        
        # 原始数据
        series = self.dp.data[self.dp.target_column]
        
        # 创建子图布局
        fig, axes = plt.subplots(3, 1, figsize=figsize)
        fig.tight_layout(pad=4.0)
        plt.subplots_adjust(hspace=0.5)
        
        # 1. 原始时间序列图
        axes[0].plot(series.index, series, color=self.color_palette['primary'], linewidth=1.5)
        axes[0].set_title('原始时间序列', fontsize=14, fontweight='bold')
        axes[0].set_xlabel('时间')
        axes[0].set_ylabel(self.dp.target_column)
        axes[0].grid(True, linestyle='--', alpha=0.7)
        
        # 添加滚动平均线
        window_size = min(30, len(series) // 10)  # 自适应窗口大小
        if window_size > 1:
            rolling_mean = series.rolling(window=window_size).mean()
            axes[0].plot(rolling_mean.index, rolling_mean, color=self.color_palette['secondary'], 
                          linewidth=2, label=f'{window_size}点移动平均')
            axes[0].legend()
        
        # 2. 分布直方图和密度曲线
        sns.histplot(series.dropna(), kde=True, ax=axes[1], color=self.color_palette['primary'], alpha=0.6)
        axes[1].set_title('数值分布', fontsize=14, fontweight='bold')
        axes[1].set_xlabel(self.dp.target_column)
        axes[1].set_ylabel('频数')
        
        # 添加均值和中位数的垂直线
        mean_val = series.mean()
        median_val = series.median()
        axes[1].axvline(mean_val, color=self.color_palette['danger'], linestyle='-', linewidth=2, label=f'均值: {mean_val:.2f}')
        axes[1].axvline(median_val, color=self.color_palette['success'], linestyle='--', linewidth=2, label=f'中位数: {median_val:.2f}')
        axes[1].legend()
        
        # 3. 自相关和偏自相关图
        try:
            lags = min(40, len(series) // 4)  # 自适应滞后阶数
            sm.graphics.tsa.plot_acf(series.dropna(), lags=lags, ax=axes[2], alpha=0.05, title='自相关函数图')
            axes[2].set_xlabel('滞后阶数')
            axes[2].set_ylabel('自相关系数')
        except Exception as e:
            print(f"绘制自相关图时出错: {str(e)}")
            axes[2].text(0.5, 0.5, '自相关计算失败', horizontalalignment='center', 
                        verticalalignment='center', transform=axes[2].transAxes)
        
        # 整体标题
        fig.suptitle(f'时间序列数据概览: {self.dp.target_column}', fontsize=16, fontweight='bold')
        
        plt.tight_layout()
        
        return fig
    
    def plot_seasonality(self, period=None, figsize=(14, 10)):
        """
        分析和可视化时间序列的季节性
        
        参数:
            period (int): 季节周期长度
            figsize (tuple): 图形大小
            
        返回:
            matplotlib.figure: 季节性分析图
        """
        if self.dp.data is None or self.dp.target_column is None:
            print("请先加载数据")
            return None
            
        series = self.dp.data[self.dp.target_column]
        
        # 尝试进行季节性分解
        try:
            result = self.dp.decompose_time_series(period=period)
            if result is None:
                print("季节性分解失败")
                return None
                
            # 创建子图
            fig, axes = plt.subplots(4, 1, figsize=figsize)
            fig.tight_layout(pad=3.0)
            plt.subplots_adjust(hspace=0.4)
            
            # 绘制四个组成部分
            components = [
                ('原始时间序列', result.observed),
                ('趋势成分', result.trend),
                ('季节性成分', result.seasonal),
                ('残差成分', result.resid)
            ]
            
            for i, (title, data) in enumerate(components):
                axes[i].plot(data.index, data, 
                           color=self.color_palette['primary' if i == 0 else 'secondary'])
                axes[i].set_title(title, fontsize=14, fontweight='bold')
                axes[i].grid(True, linestyle='--', alpha=0.7)
                if i == len(components) - 1:
                    axes[i].axhline(y=0, color=self.color_palette['dark'], linestyle='--', alpha=0.5)
            
            # 设置整体标题
            used_period = period if period else result.seasonal.shape[0]
            fig.suptitle(f'时间序列分解 (季节周期: {used_period})', fontsize=16, fontweight='bold')
            
            return fig
            
        except Exception as e:
            print(f"季节性分析时出错: {str(e)}")
            return None
    
    def plot_stationarity_test(self, window=10, figsize=(12, 8)):
        """
        进行平稳性分析和可视化
        
        参数:
            window (int): 滚动窗口大小
            figsize (tuple): 图形大小
            
        返回:
            matplotlib.figure: 平稳性分析图
        """
        if self.dp.data is None or self.dp.target_column is None:
            print("请先设置数据")
            return None
            
        # 获取结果
        result = self.analyzer.check_stationarity(window)
        if result is None:
            return None
            
        series = self.dp.data[self.dp.target_column]
        rolling_mean = result['rolling_mean']
        rolling_std = result['rolling_std']
        adf_result = result['adf_test']
        
        # 创建图表
        fig = plt.figure(figsize=figsize)
        ax1 = fig.add_subplot(211)
        ax2 = fig.add_subplot(212)
        
        # 绘制原始序列和滚动统计量
        ax1.plot(series.index, series, label='原始数据', color=self.color_palette['primary'])
        ax1.plot(rolling_mean.index, rolling_mean, 
                label=f'滚动平均 (窗口={window})', color=self.color_palette['secondary'])
        ax1.plot(rolling_std.index, rolling_std, 
                label=f'滚动标准差 (窗口={window})', color=self.color_palette['danger'])
        
        ax1.set_title('时间序列平稳性分析', fontsize=14, fontweight='bold')
        ax1.set_xlabel('时间')
        ax1.set_ylabel(self.dp.target_column)
        ax1.legend()
        ax1.grid(True)
        
        # 绘制差分序列
        diff = series.diff().dropna()
        diff_mean = diff.rolling(window=window).mean()
        diff_std = diff.rolling(window=window).std()
        
        ax2.plot(diff.index, diff, label='差分序列', color=self.color_palette['primary'])
        ax2.plot(diff_mean.index, diff_mean, 
                label=f'差分滚动平均 (窗口={window})', color=self.color_palette['secondary'])
        ax2.plot(diff_std.index, diff_std, 
                label=f'差分滚动标准差 (窗口={window})', color=self.color_palette['danger'])
        
        ax2.set_title('差分序列分析', fontsize=14, fontweight='bold')
        ax2.set_xlabel('时间')
        ax2.set_ylabel('差分值')
        ax2.legend()
        ax2.grid(True)
        
        plt.tight_layout()
        
        # 添加ADF检验结果
        plt.figtext(0.02, 0.02, 
                   f"ADF检验结果:\n"
                   f"p值: {adf_result['p_value']:.4f}\n"
                   f"序列{'是' if adf_result['is_stationary'] else '不是'}平稳的",
                   bbox=dict(facecolor='white', alpha=0.8))
        
        return fig

    def create_interactive_dashboard(self):
        """
        创建交互式仪表盘
        
        返回:
            dash.Dash: Dash应用实例
        """
        try:
            # 检查数据和分析器是否已准备好
            if self.dp.data is None or self.dp.target_column is None:
                print("请先加载并设置数据")
                return None
                
            if not hasattr(self.analyzer, 'forecast_results') or not self.analyzer.forecast_results:
                print("请先运行模型预测")
                return None
            
            # 创建Dash应用
            app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
            
            # 创建图表对象
            overview_fig = self._create_overview_figure() or go.Figure()
            model_comp_fig = self._create_model_comparison_figure() or go.Figure()
            anomaly_fig = self._create_anomaly_figure() or go.Figure()
            
            # 创建仪表盘布局
            cards = []
            
            # 标题
            cards.append(html.H1("时间序列分析仪表盘", className="text-center my-4"))
            
            # 数据概览卡片
            # 检查图形是否包含数据
            def has_data(fig):
                return len(fig.data) > 0 if fig and hasattr(fig, 'data') else False
            
            # 数据概览卡片
            if has_data(overview_fig):
                cards.append(dbc.Card([
                    dbc.CardHeader("数据概览"),
                    dbc.CardBody([
                        dcc.Graph(id='overview-graph', figure=overview_fig)
                    ])
                ], className="mb-4"))
            
            # 模型比较卡片
            if has_data(model_comp_fig):
                cards.append(dbc.Card([
                    dbc.CardHeader("模型性能比较"),
                    dbc.CardBody([
                        dcc.Graph(id='model-comparison', figure=model_comp_fig)
                    ])
                ], className="mb-4"))
            
            # 异常检测卡片
            if has_data(anomaly_fig):
                cards.append(dbc.Card([
                    dbc.CardHeader("异常检测"),
                    dbc.CardBody([
                        dcc.Graph(id='anomaly-detection', figure=anomaly_fig)
                    ])
                ]))
            
            # 设置应用布局
            app.layout = html.Div([
                dbc.Container(cards)
            ])
            
            return app
            
        except Exception as e:
            print(f"创建仪表盘时出错: {str(e)}")
            return None
            
    def _create_overview_figure(self):
        """创建数据概览图"""
        if self.dp.data is None or self.dp.target_column is None:
            return go.Figure()
            
        fig = go.Figure()
        series = self.dp.data[self.dp.target_column]
        
        fig.add_trace(go.Scatter(
            x=series.index,
            y=series.values,
            name='原始数据',
            line=dict(color=self.color_palette['primary'])
        ))
        
        # 添加滚动平均线
        window_size = min(30, len(series) // 10)
        if window_size > 1:
            rolling_mean = series.rolling(window=window_size).mean()
            fig.add_trace(go.Scatter(
                x=rolling_mean.index,
                y=rolling_mean.values,
                name=f'{window_size}点移动平均',
                line=dict(color=self.color_palette['secondary'])
            ))
            
        fig.update_layout(
            title='时间序列数据概览',
            xaxis_title='时间',
            yaxis_title=self.dp.target_column,
            showlegend=True
        )
        
        return fig
        
    def _create_model_comparison_figure(self):
        """创建模型比较图"""
        if not hasattr(self.analyzer, 'forecast_results'):
            return go.Figure()
            
        if not hasattr(self.analyzer, 'forecast_results'):
            return go.Figure()
        
        try:
            # 创建性能比较数据
            data = []
            for model_name, results in self.analyzer.forecast_results.items():
                metrics = results['metrics']
                data.append({
                    'Model': model_name.upper(),
                    'Train MAE': metrics['train_mae'],
                    'Test MAE': metrics['test_mae'],
                    'Train RMSE': metrics['train_rmse'],
                    'Test RMSE': metrics['test_rmse']
                })

            df = pd.DataFrame(data)
            
            # 创建性能比较图表
            fig = go.Figure()
            
            # 添加MAE柱状图
            fig.add_trace(go.Bar(
                name='Train MAE',
                x=df['Model'],
                y=df['Train MAE'],
                offsetgroup=0
            ))
            fig.add_trace(go.Bar(
                name='Test MAE',
                x=df['Model'],
                y=df['Test MAE'],
                offsetgroup=0
            ))
            
            # 添加RMSE柱状图
            fig.add_trace(go.Bar(
                name='Train RMSE',
                x=df['Model'],
                y=df['Train RMSE'],
                offsetgroup=1
            ))
            fig.add_trace(go.Bar(
                name='Test RMSE',
                x=df['Model'],
                y=df['Test RMSE'],
                offsetgroup=1
            ))
            
            # 更新布局
            fig.update_layout(
                title='模型性能比较',
                barmode='group',
                xaxis_title='模型',
                yaxis_title='指标值'
            )
            
            return fig
            
        except Exception as e:
            print(f"创建模型比较图表时出错: {str(e)}")
            return go.Figure()
        
        fig.update_layout(
            xaxis_title='模型',
            yaxis_title='指标值'
        )
        
        return fig
        
    def _create_anomaly_figure(self):
        """创建异常检测图"""
        if self.dp.data is None or self.dp.target_column is None:
            return go.Figure()
            
        fig = go.Figure()
        series = self.dp.data[self.dp.target_column]
        
        # 绘制原始数据
        fig.add_trace(go.Scatter(
            x=series.index,
            y=series.values,
            name='时间序列数据',
            line=dict(color=self.color_palette['primary'])
        ))
        
        # 检测异常点
        anomalies = self.analyzer.detect_anomalies(method='zscore', threshold=3)
        if len(anomalies) > 0:
            fig.add_trace(go.Scatter(
                x=anomalies.index,
                y=series[anomalies.index],
                mode='markers',
                name='异常点',
                marker=dict(
                    color=self.color_palette['danger'],
                    size=10,
                    symbol='x'
                )
            ))
            
        fig.update_layout(
            title='异常检测结果',
            xaxis_title='时间',
            yaxis_title=self.dp.target_column,
            showlegend=True
        )
        
        return fig

    def run_dashboard(self):
        """运行交互式仪表盘"""
        app = self.create_interactive_dashboard()
        if app:
            print("启动交互式仪表盘，请访问 http://127.0.0.1:8050")
            app.run_server(debug=True)
        else:
            print("仪表盘创建失败")